package top.broncho.anpods.util

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.os.ParcelUuid
import androidx.annotation.CheckResult
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.filter
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.warn
import top.broncho.anpods.AnPodsService
import top.broncho.anpods.model.BatteryState

val airPodsBatteryState = MutableLiveData<BatteryState>()

@CheckResult
@ExperimentalCoroutinesApi
fun Context.batteryState(): Flow<ScanResult> = callbackFlow<ScanResult> {
    checkMainThread()
    val manager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    val scanCallback = object : ScanCallback() {
        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
            super.onBatchScanResults(results)
            results?.forEach {
                onScanResult(-1, it)
            }
        }

        override fun onScanResult(callbackType: Int, result: ScanResult?) {
            super.onScanResult(callbackType, result)
            result ?: return
            safeOffer(result)
        }

        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            ankoLogger.warn { "onScanFailed: errorCode = $errorCode" }
        }
    }
    val manufacturerData = ByteArray(27).apply {
        this[0] = 7
        this[1] = 25
    }
    val manufacturerDataMask = ByteArray(27).apply {
        this[0] = -1
        this[1] = -1
    }
    val scanFilter = ScanFilter.Builder()
        .setManufacturerData(76, manufacturerData, manufacturerDataMask)
        .build()
    val filters: List<ScanFilter> = listOf(scanFilter)
    val settings = ScanSettings.Builder()
        .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
        .setReportDelay(2)
        .build()
    manager.adapter.bluetoothLeScanner.startScan(filters, settings, scanCallback)
    awaitClose { manager.adapter.bluetoothLeScanner.stopScan(scanCallback) }
}.conflate()
    .filter {
        it.rssi > -60
    }
    .filter {
        val data = it.scanRecord?.getManufacturerSpecificData(76)
        data != null && data.size == 27 && !data.decodeHex().isNullOrEmpty()
    }


fun ScanResult.parse(overrideModel: String): BatteryState {
    val signal = scanRecord!!.getManufacturerSpecificData(76)!!.decodeHex()!!
    AnkoLogger<AnPodsService>().warn {
        "AnkoLogger 0: ${signal[0].toString().toInt(16)}" +
                ", 1: ${signal[1].toString().toInt(16)}" +
                ", 2: ${signal[2].toString().toInt(16)}" +
                ", 3: ${signal[3].toString().toInt(16)}" +
                ", 4: ${signal[4].toString().toInt(16)}" +
                ", 5: ${signal[5].toString().toInt(16)}" +
                ", 6: ${signal[6].toString().toInt(16)}" +
                ", 8: ${signal[8].toString().toInt(16)}" +
                ", 9: ${signal[9].toString().toInt(16)}" +
                ", 10: ${signal[10].toString().toInt(16)}" +
                ", 11: ${signal[11].toString().toInt(16)}" +
                ", 16: ${signal[16].toString().toInt(16)}"
    }
    //left and right airpod (0-10 batt; 15=disconnected)
    val leftBattery =
        if (signal.isFlipped()) signal[12].toString().toInt(16) else signal[13].toString().toInt(
            16
        )

    val rightBattery =
        if (signal.isFlipped()) signal[13].toString().toInt(16) else signal[12].toString().toInt(
            16
        )
    //case (0-10 batt; 15=disconnected)
    val caseBattery = signal[15].toString().toInt(16)

    //charge status (bit 0=left; bit 1=right; bit 2=case)
    val chargeStatus = signal[14].toString().toInt(16)

    val isLeftCharge = chargeStatus and 1 != 0
    val isRightCharge = chargeStatus and 2 != 0
    val isCaseCharge = chargeStatus and 4 != 0

    val model = if (overrideModel == "auto") {
        //detect if these are AirPods pro or regular ones
        if (signal[7] == 'E') MODEL_AIR_PODS_PRO else MODEL_AIR_PODS_NORMAL
    } else {
        overrideModel
    }
    return BatteryState(
        System.currentTimeMillis(),
        leftBattery,
        rightBattery,
        caseBattery,
        isLeftCharge,
        isRightCharge,
        isCaseCharge,
        model
    )
}

const val MODEL_AIR_PODS_NORMAL = "airpods12"
const val MODEL_AIR_PODS_1 = "airpods1"
const val MODEL_AIR_PODS_2 = "airpods2"
const val MODEL_AIR_PODS_PRO = "airpodspro"

private val AIR_PODS_UUID_ARRAY = arrayOf(
    ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"),
    ParcelUuid.fromString("2a72e02b-7b99-778f-014d-ad0b7221ec74")
)

private val hexCharset =
    charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')

fun BluetoothDevice.checkUUID(): Boolean {
    val uuidArray = uuids ?: return false
    for (u in uuidArray) {
        if (AIR_PODS_UUID_ARRAY.contains(u)) return true
    }
    return false
}

fun ByteArray.decodeHex(): String? {
    val ret = CharArray(size * 2)
    for (i in indices) {
        val b: Int = this[i].toInt() and 0xFF
        ret[i * 2] = hexCharset[b ushr 4]
        ret[i * 2 + 1] = hexCharset[b and 0x0F]
    }
    return String(ret)
}

fun String.isFlipped(): Boolean {
    return (this[10].toString().toInt(16) + 0x10).toString(2)[3] == '0'
}